package com.linkhub.oms.admin.modules.wms.service.impl;

import cn.hutool.core.util.IdUtil;
import com.linkhub.oms.admin.common.exception.ExceptionFactory;
import com.linkhub.oms.admin.common.util.I18nMessageUtil;
import com.linkhub.oms.admin.modules.product.domain.param.CreateProcessOrderParam;
import com.linkhub.oms.admin.modules.stock.service.StockBizService;
import com.google.common.collect.Lists;
import com.linkhub.oms.admin.common.exception.ExceptionFactory;
import com.linkhub.oms.admin.common.util.I18nMessageUtil;
import com.linkhub.oms.admin.modules.product.domain.param.CreateProcessOrderParam;
import com.linkhub.oms.admin.modules.satoken.session.LhSessionContext;
import com.linkhub.oms.admin.modules.stock.domain.dto.PreemptionDTO;
import com.linkhub.oms.admin.modules.stock.exception.PreemptionFailException;
import com.linkhub.oms.admin.modules.stock.service.StockHandler;
import com.linkhub.oms.admin.modules.user.account.domain.dto.UserAccountDTO;
import com.linkhub.oms.admin.modules.user.account.service.IUserAccountService;
import com.linkhub.oms.admin.modules.user.accountflow.domain.dto.UserAccountFlowDTO;
import com.linkhub.oms.admin.modules.user.accountflow.service.IUserAccountFlowBizService;
import com.linkhub.oms.admin.modules.wms.service.WmsProcessOrderService;
import com.linkhub.oms.common.constant.WmsInfoConstant;
import com.linkhub.oms.common.enums.mp.DocumentTypeEnum;
import com.linkhub.oms.common.enums.mp.ExpenseTypeEnum;
import com.linkhub.oms.common.enums.mp.TransactionTypeEnum;
import com.linkhub.oms.common.exception.BizException;
import com.linkhub.oms.common.util.WmsUtil;
import com.linkhub.oms.dao.mpservice.ILocalProductCombinationInfoService;
import com.linkhub.oms.dao.mpservice.IProcessOrderRecordService;
import com.linkhub.oms.data.sync.api.wms.domain.dto.WmsProcessOrderCreateDTO;
import com.linkhub.oms.data.sync.api.wms.domain.response.StoreProcessCreateResponse;
import com.linkhub.oms.data.sync.service.WMSApiService;
import com.linkhub.oms.entity.LocalProductCombinationInfoEntity;
import com.linkhub.oms.entity.ProcessOrderRecordEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.linkhub.oms.common.constant.WmsInfoConstant.WMS_PROCESS_ORDER_BASE_FEE;
import static com.linkhub.oms.common.constant.WmsInfoConstant.WMS_PROCESS_ORDER_EXTEND_FEE;

/**
 * @author Jaycle
 * @version 1.0.0
 * @Description WmsProcessOrderServiceImpl.java
 * @createTime 2024/06/15 17:58
 */

@Slf4j
@Service
public class WmsProcessOrderServiceImpl implements WmsProcessOrderService {

    @Resource
    private ILocalProductCombinationInfoService localProductCombinationInfoService;

    @Resource
    private StockBizService stockBizService;

    @Resource
    private WMSApiService wmsApiService;
    @Resource
    private StockHandler stockHandler;

    @Resource
    private IUserAccountFlowBizService userAccountFlowService;

    @Resource
    private IUserAccountService userAccountService;

    @Resource
    private IProcessOrderRecordService processOrderRecordService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Void createProcessOrder( CreateProcessOrderParam createProcessOrderParam ) {
        final String userId = createProcessOrderParam.getUserId();
        final String processSkuId = createProcessOrderParam.getSkuId();
        final Integer nums = createProcessOrderParam.getNums();
        // 查询组合商品对应的单个商品信息
        List<LocalProductCombinationInfoEntity> localProductCombinationInfoEntityList = localProductCombinationInfoService
                .queryCombinationInfoBySkuId(userId, processSkuId);

        // 获取到单个skuId对应的数量并计算出每个单个产品应预占的数量
        Map<String, Integer> skuIdToNumsMap = localProductCombinationInfoEntityList.stream()
                .collect(Collectors.toMap(LocalProductCombinationInfoEntity::getSingleSkuId,
                        combinationInfo -> combinationInfo.getSingleSkuNums() * nums));

        String processOrderCode = IdUtil.getSnowflake().nextIdStr();
        // 查询单个商品库存是否满足加工要求，满足则预占库存，不满足则报错返回
        List<PreemptionDTO> preemptionDTOS = Lists.newArrayList();
        for (Map.Entry<String, Integer> preemptionSku : skuIdToNumsMap.entrySet()) {
            PreemptionDTO preemptionDTO = new PreemptionDTO();
            preemptionDTO.setUserId(LhSessionContext.getUserId());
            preemptionDTO.setSkuId(preemptionSku.getKey());
            preemptionDTO.setStashName(WmsInfoConstant.WAREHOUSE_CODE);
            preemptionDTO.setSkuQuantity(preemptionSku.getValue());
            preemptionDTO.setBusinessType(WmsInfoConstant.CNJG);
            preemptionDTO.setBusinessNo(processOrderCode);
            preemptionDTOS.add(preemptionDTO);
        }
        try {
            stockHandler.combineProcessingCreated(preemptionDTOS);
        } catch (PreemptionFailException preemptionFailException) {
            throw ExceptionFactory.getI18nBizException("process.order.out.of.stock");
        }

        // 冻结加工费
        freezeProcessOrderFee(createProcessOrderParam, processOrderCode);

        /**
         * 创建WMS加工单
         */
        WmsProcessOrderCreateDTO wmsProcessOrderCreateDTO = new WmsProcessOrderCreateDTO();
        wmsProcessOrderCreateDTO.setOwnerCode(WmsInfoConstant.OWNER_CODE);
        wmsProcessOrderCreateDTO.setWarehouseCode(WmsInfoConstant.WAREHOUSE_CODE);
        wmsProcessOrderCreateDTO.setProcessOrderCode(processOrderCode); // ERP加工单代码
        wmsProcessOrderCreateDTO.setOrderType(WmsInfoConstant.CNJG); // 单据类型，固定为CNJG
        wmsProcessOrderCreateDTO.setServiceType(WmsInfoConstant.COMBINATION_PROCESSING); // 加工类型 1=组合加工 2=拆解加工
        wmsProcessOrderCreateDTO.setPlanQty(String.valueOf(nums)); // 计划成品数量

        // 原料项列表
        WmsProcessOrderCreateDTO.MaterialItems materialItems = new WmsProcessOrderCreateDTO.MaterialItems();
        List<WmsProcessOrderCreateDTO.MaterialItems.MaterialItem> materialItemList = new ArrayList<>();
        Map<String, Integer> collect = localProductCombinationInfoEntityList.stream()
                .collect(Collectors.toMap(LocalProductCombinationInfoEntity::getSingleSkuId,
                        combinationInfo -> combinationInfo.getSingleSkuNums()));
        for (Map.Entry<String, Integer> entry : collect.entrySet()) {
            String skuId = entry.getKey();
            Integer singleSkuNums = entry.getValue();
            WmsProcessOrderCreateDTO.MaterialItems.MaterialItem materialItem = new WmsProcessOrderCreateDTO.MaterialItems.MaterialItem();
            materialItem.setItemCode(WmsUtil.createItemCode(userId, skuId)); // 被加工的单个产品ID
            materialItem.setQuantity(String.valueOf(singleSkuNums * nums)); // 被加工的单个产品消耗数量
            materialItem.setRatioQty(String.valueOf(singleSkuNums)); // 配比数量
            materialItem.setUserDefined1(userId);
            materialItemList.add(materialItem);
        }
        materialItems.setItem(materialItemList);
        wmsProcessOrderCreateDTO.setMaterialitems(materialItems);

        // 成品项
        WmsProcessOrderCreateDTO.ProductItems productItems = new WmsProcessOrderCreateDTO.ProductItems();
        WmsProcessOrderCreateDTO.ProductItems.ProductItem productItem = new WmsProcessOrderCreateDTO.ProductItems.ProductItem();
        productItem.setItemCode(WmsUtil.createItemCode(userId, processSkuId)); // 加工后的产品ID
        productItem.setQuantity(String.valueOf(nums)); // 加工后的产品数量，与planQty一致
        productItem.setUserDefined1(userId);
        productItems.setItem(productItem);
        wmsProcessOrderCreateDTO.setProductitems(productItems);

        StoreProcessCreateResponse response = wmsApiService.createStoreProcess(wmsProcessOrderCreateDTO);
        if (!response.success()) {
            throw new BizException(I18nMessageUtil.getMessage("local.product.create.store.process.wms.failed", response.getResponse().getMessage()));
        }

        ProcessOrderRecordEntity processOrderRecordEntity = new ProcessOrderRecordEntity();
        processOrderRecordEntity.setUserId(userId);
        processOrderRecordEntity.setSkuId(processSkuId);
        processOrderRecordEntity.setSysProcessOrderCode(processOrderCode);
        processOrderRecordEntity.setSkuQuantity(nums);
        processOrderRecordService.save(processOrderRecordEntity);
        return null;
    }

    /**
     * 冻结加工单费用
     * @param createProcessOrderParam
     * @param processOrderCode
     */
    private void freezeProcessOrderFee(CreateProcessOrderParam createProcessOrderParam, String processOrderCode){
        float fee = queryProcessOrderFee(createProcessOrderParam);
        BigDecimal precessFee = BigDecimal.valueOf(fee);
        UserAccountDTO userAccount =userAccountService.updateAccountAmount(createProcessOrderParam.getUserId(),precessFee,TransactionTypeEnum.FREEZE);
        UserAccountFlowDTO accountFlow = new UserAccountFlowDTO();
        accountFlow.setUserId(createProcessOrderParam.getUserId());
        accountFlow.setTransactionId(IdUtil.getSnowflake().nextIdStr());
        accountFlow.setTransactionType(TransactionTypeEnum.FREEZE);
        accountFlow.setDocumentId(processOrderCode);
        accountFlow.setDocumentType(DocumentTypeEnum.PROCESS_ORDER);
        accountFlow.setRemainingBalance(userAccount.getTotalBalance());
        accountFlow.setExpenseType(ExpenseTypeEnum.PROCESSING);
        accountFlow.setAmount(precessFee);
        userAccountFlowService.saveAccountFlow(accountFlow);
    }

    @Override
    public Float queryProcessOrderFee( CreateProcessOrderParam createProcessOrderParam ) {
        /**
         * 组合商品加工费逻辑
         * 组合商品包含的产品数为n
         * 需要加工的组合商品数量为x
         * [0.3 + (n-2)*0.05]*x
         */
        // 查询组合商品对应的单个商品信息
        List<LocalProductCombinationInfoEntity> localProductCombinationInfoEntityList = localProductCombinationInfoService
                .queryCombinationInfoBySkuId(createProcessOrderParam.getUserId(), createProcessOrderParam.getSkuId());

        // 获取到每个组合商品需要的单个商品总数
        int totalSingleSkuNums = localProductCombinationInfoEntityList.stream()
                .mapToInt(LocalProductCombinationInfoEntity::getSingleSkuNums)
                .sum();

        // 计算加工商品所需费用
        Float fee = (WMS_PROCESS_ORDER_BASE_FEE + (totalSingleSkuNums - 2) * WMS_PROCESS_ORDER_EXTEND_FEE) * createProcessOrderParam.getNums();

        return fee;
    }
}
